<!DOCTYPE html>
<html>
<head>
<title>RTCPeerConnection-ontrack</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
</head>
<body>
<script>

// TODO(hbos): Consider removing this file, it duplicates wpt tests.

promise_test(async t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());
  const pc2 = new RTCPeerConnection();
  t.add_cleanup(() => pc2.close());
  let eventSequence = [];
  return createStreams({audio:true, video:false}, 1)
    .then(function(streams) {
      pc.addStream(streams[0]);
      let resolvers = Resolver.createResolvers(2);
      pc2.ontrack = function() {
        eventSequence.push('ontrack');
        resolvers.shift().resolve();
      }
      createAndSetRemoteDescription(pc, pc2).then(function() {
        eventSequence.push('setRemoteDescription');
        resolvers.shift().resolve();
      });
      return Promise.all(Resolver.promises(resolvers));
    }).then(function() {
      let expectedEventSequence = [ 'ontrack', 'setRemoteDescription' ];
      assert_array_equals(expectedEventSequence, eventSequence);
    });
}, 'ontrack fires before negotiation done.');

promise_test(async t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());
  const pc2 = new RTCPeerConnection();
  t.add_cleanup(() => pc2.close());
  let eventSequence = [];
  return createStreams({audio:true, video:false}, 1)
    .then(function(streams) {
      pc.addStream(streams[0]);
      let resolvers = Resolver.createResolvers(2);
      pc2.onaddstream = function() {
        eventSequence.push('onaddstream');
        resolvers.shift().resolve();
      }
      createAndSetRemoteDescription(pc, pc2).then(function() {
        eventSequence.push('setRemoteDescription');
        resolvers.shift().resolve();
      });
      return Promise.all(Resolver.promises(resolvers));
    }).then(function() {
      let expectedEventSequence = [ 'onaddstream', 'setRemoteDescription' ];
      assert_array_equals(eventSequence, expectedEventSequence);
    });
}, 'onaddstream fires before negotation done.');

promise_test(async t => {
  const pc = new RTCPeerConnection({sdpSemantics:'plan-b'});
  t.add_cleanup(() => pc.close());
  const pc2 = new RTCPeerConnection({sdpSemantics:'plan-b'});
  t.add_cleanup(() => pc2.close());
  let eventSequence = [];
  return createStreams({audio:true, video:false}, 2)
    .then(function(streams) {
      pc.addStream(streams[0]);
      pc.addStream(streams[1]);
      let resolvers = Resolver.createResolvers(4);
      pc2.onaddstream = function(event) {
        eventSequence.push('stream:' + event.stream.id);
        resolvers.shift().resolve();
      };
      pc2.ontrack = function(event) {
        eventSequence.push('track:' + event.track.id);
        resolvers.shift().resolve();
      };
      return Promise.all(
          Resolver.promises(resolvers).concat(
              createAndSetRemoteDescription(pc, pc2)));
    }).then(function() {
      let expectedEventSequence =
          [ 'stream:' + pc2.getRemoteStreams()[0].id,
            'stream:' + pc2.getRemoteStreams()[1].id,
            'track:' + pc2.getRemoteStreams()[0].getAudioTracks()[0].id,
            'track:' + pc2.getRemoteStreams()[1].getAudioTracks()[0].id ];
      assert_array_equals(eventSequence, expectedEventSequence);
    });
}, 'ontrack and onaddstream fires in the expected relative order.');

promise_test(async t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());
  const pc2 = new RTCPeerConnection();
  t.add_cleanup(() => pc2.close());
  let eventSequence = [];
  return createStreams({audio:true, video:false}, 1)
    .then(function(streams) {
      pc.addStream(streams[0]);
      let resolvers = Resolver.createResolvers(3);
      let callback = function() {
        if (eventSequence.length == 0) {
          setTimeout(function() {
            eventSequence.push('new task');
            resolvers.shift().resolve();
          }, 0);
        }
        eventSequence.push('track or stream fired');
        resolvers.shift().resolve();
      }
      pc2.onaddstream = callback;
      pc2.ontrack = callback;
      return Promise.all(
          Resolver.promises(resolvers).concat(
              createAndSetRemoteDescription(pc, pc2)));
    }).then(function() {
      assert_array_equals(
          eventSequence,
        [ 'track or stream fired', 'track or stream fired', 'new task' ]);
    });
}, 'ontrack and onaddstream fires in the same task.');

promise_test(async t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());
  const pc2 = new RTCPeerConnection();
  t.add_cleanup(() => pc2.close());
  return createStreams({audio:true, video:false}, 1)
    .then(function(streams) {
      pc.addStream(streams[0]);
      let resolver = new Resolver();
      pc2.ontrack = function(event) {
        let remoteStream = pc2.getRemoteStreams()[0];
        let remoteAudioTrack = remoteStream.getAudioTracks()[0];
        assert_equals(event.track, remoteAudioTrack);
        resolver.resolve();
      };
      return Promise.all([ resolver.promise,
                           createAndSetRemoteDescription(pc, pc2) ]);
    });
}, 'ontrack event\'s track matches track in getRemoteStreams().');

promise_test(async t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());
  const pc2 = new RTCPeerConnection();
  t.add_cleanup(() => pc2.close());
  return createStreams({audio:true, video:false}, 1)
    .then(function(streams) {
      pc.addStream(streams[0]);
      let resolver = new Resolver();
      pc2.ontrack = function(event) {
        let receiver = pc2.getReceivers()[0];
        assert_equals(event.receiver, receiver);
        resolver.resolve();
      };
      return Promise.all([ resolver.promise,
                           createAndSetRemoteDescription(pc, pc2) ]);
    });
}, 'ontrack event\'s receiver matches getReceivers().');

promise_test(async t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());
  const pc2 = new RTCPeerConnection();
  t.add_cleanup(() => pc2.close());
  return createStreams({audio:true, video:false}, 1)
    .then(function(streams) {
      pc.addStream(streams[0]);
      let resolver = new Resolver();
      pc2.ontrack = function(event) {
        assert_array_equals(event.streams, pc2.getRemoteStreams())
        resolver.resolve();
      };
      return Promise.all([ resolver.promise,
                           createAndSetRemoteDescription(pc, pc2) ]);
    });
}, 'ontrack event\'s streams match getRemoteStreams().');

promise_test(async t => {
  const pc = new RTCPeerConnection();
  t.add_cleanup(() => pc.close());
  const pc2 = new RTCPeerConnection();
  t.add_cleanup(() => pc2.close());
  let firedForAudioTrack = false;
  let firedForVideoTrack = false;
  return createStreams({audio:true, video:true}, 1)
    .then(function(streams) {
      pc.addStream(streams[0]);
      let resolvers = Resolver.createResolvers(2);
      pc2.ontrack = function(event) {
        let remoteStream = pc2.getRemoteStreams()[0];
        if (event.track == remoteStream.getAudioTracks()[0])
          firedForAudioTrack = true;
        else if (event.track == remoteStream.getVideoTracks()[0])
          firedForVideoTrack = true;
        resolvers.shift().resolve();
      };
      return Promise.all(
          Resolver.promises(resolvers).concat(
              createAndSetRemoteDescription(pc, pc2)));
    }).then(function() {
      assert_true(firedForAudioTrack && firedForVideoTrack);
    });
}, 'ontrack event fires twice for a stream containing two tracks.');

/**
 * Helper functions.
 */

function createStreams(constraints, numStreams, streamsSoFar = []) {
  if (numStreams == 0) {
    return Promise.resolve(streamsSoFar);
  }
  return navigator.mediaDevices.getUserMedia(constraints)
    .then(function(stream) {
      return createStreams(constraints,
                           numStreams - 1,
                           streamsSoFar.concat([stream]));
    });
}

/**
 * Makes the setup step necessary to cause pc_b to emit events.
 * Note: This does not set up a connection.
 * Returns the promise resulting from pc_b.setRemoteDescription.
 */
function createAndSetRemoteDescription(pc_a, pc_b) {
  return pc_a.createOffer()
  .then(function(offer) {
    return pc_b.setRemoteDescription(offer);
  })
}

/**
 * The resolver has a |promise| that can be resolved or rejected using |resolve|
 * or |reject|.
 */
class Resolver {
  constructor() {
    let promiseResolve;
    let promiseReject;
    this.promise = new Promise(function(resolve, reject) {
      promiseResolve = resolve;
      promiseReject = reject;
    });
    this.resolve = promiseResolve;
    this.reject = promiseReject;
  }

  static createResolvers(count) {
    let result = [];
    for (let i = 0; i < count; ++i)
      result.push(new Resolver());
    return result;
  }

  static promises(resolvers) {
    let promises = [];
    for (let i = 0; i < resolvers.length; ++i) {
      promises.push(resolvers[i].promise);
    }
    return promises;
  }
}
</script>
</body>
</html>
